// Copyright © 2013-2017 Esko Luontola and other Retrolambda contributors // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package net.orfjackal.retrolambda.maven; import com.google.common.base.*; import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; import net.orfjackal.retrolambda.*; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.*; import org.apache.maven.plugins.annotations.*; import org.apache.maven.project.MavenProject; import org.apache.maven.toolchain.*; import java.io.*; import java.util.*; import static org.twdata.maven.mojoexecutor.MojoExecutor.*; abstract class ProcessClassesMojo extends AbstractMojo { private static final Map<String, Integer> targetBytecodeVersions = ImmutableMap.of( "1.5", 49, "1.6", 50, "1.7", 51, "1.8", 52 ); @Component ToolchainManager toolchainManager; @Component private BuildPluginManager pluginManager; @Parameter(defaultValue = "${session}", readonly = true) private MavenSession session; @Parameter(defaultValue = "${project}", readonly = true) protected MavenProject project; /** * Directory of the Java 8 installation for running Retrolambda. * The JRE to be used will be determined in priority order: * <ol> * <li>This parameter</li> * <li><a href="http://maven.apache.org/plugins/maven-toolchains-plugin/toolchains/jdk.html">JDK toolchain</a></li> * <li>Same as Maven</li> * </ol> * * @since 1.2.0 */ @Parameter(property = "java8home", required = false) public File java8home; /** * The Java version targeted by the bytecode processing. Possible values are * 1.5, 1.6, 1.7 and 1.8. After processing the classes will be compatible * with the target JVM provided the known limitations are considered. See * <a href="https://github.com/orfjackal/retrolambda">project documentation</a> * for more details. * * @since 1.2.0 */ @Parameter(defaultValue = "1.7", property = "retrolambdaTarget", required = true) public String target; /** * Whether to backport default methods and static methods on interfaces. * LIMITATIONS: All backported interfaces and all classes which implement * them or call their static methods must be backported together, * with one execution of Retrolambda. * * @since 2.0.0 */ @Parameter(defaultValue = "false", property = "retrolambdaDefaultMethods", required = true) public boolean defaultMethods; /** * Reduces the amount of logging. * * @since 2.4.0 */ @Parameter(defaultValue = "false", property = "retrolambdaQuiet", required = true) public boolean quiet; /** * Forces Retrolambda to run in a separate process. The default is not to fork, * in which case Maven has to run under Java 8, or this plugin will fall back * to forking. The forked process uses a Java agent hook for capturing the lambda * classes generated by Java 8, whereas the non-forked version hooks into internal * Java APIs, making it more susceptible to breaking between Java releases. * * @since 1.6.0 */ @Parameter(defaultValue = "false") public boolean fork; protected abstract File getInputDir(); protected abstract File getOutputDir(); protected abstract List<String> getClasspathElements() throws DependencyResolutionRequiredException; @Override public void execute() throws MojoExecutionException { validateTarget(); validateFork(); if (fork) { processClassesInForkedProcess(); } else { processClassesInCurrentProcess(); } } private void validateTarget() throws MojoExecutionException { if (!targetBytecodeVersions.containsKey(target)) { String possibleValues = Joiner.on(", ").join(new TreeSet<String>(targetBytecodeVersions.keySet())); throw new MojoExecutionException( "Unrecognized target '" + target + "'. Possible values are " + possibleValues); } } private void validateFork() { if (!fork && !Main.isRunningJava8()) { getLog().warn("Maven is not running under Java 8 - forced to fork the process"); fork = true; } } private void processClassesInCurrentProcess() throws MojoExecutionException { getLog().info("Processing classes with Retrolambda"); try { Properties p = new Properties(); p.setProperty(SystemPropertiesConfig.BYTECODE_VERSION, "" + targetBytecodeVersions.get(target)); p.setProperty(SystemPropertiesConfig.DEFAULT_METHODS, "" + defaultMethods); p.setProperty(SystemPropertiesConfig.QUIET, "" + quiet); p.setProperty(SystemPropertiesConfig.INPUT_DIR, getInputDir().getAbsolutePath()); p.setProperty(SystemPropertiesConfig.OUTPUT_DIR, getOutputDir().getAbsolutePath()); p.setProperty(SystemPropertiesConfig.CLASSPATH, getClasspath()); Retrolambda.run(new SystemPropertiesConfig(p)); } catch (Throwable t) { throw new MojoExecutionException("Failed to run Retrolambda", t); } } private void processClassesInForkedProcess() throws MojoExecutionException { String version = getRetrolambdaVersion(); getLog().info("Retrieving Retrolambda " + version); retrieveRetrolambdaJar(version); getLog().info("Processing classes with Retrolambda"); String retrolambdaJar = getRetrolambdaJarPath(); File classpathFile = getClasspathFile(); try { executeMojo( plugin(groupId("org.apache.maven.plugins"), artifactId("maven-antrun-plugin"), version("1.7")), goal("run"), configuration(element( "target", element("exec", attributes( attribute("executable", getJavaCommand()), attribute("failonerror", "true")), element("arg", attribute("value", "-Dretrolambda.bytecodeVersion=" + targetBytecodeVersions.get(target))), element("arg", attribute("value", "-Dretrolambda.defaultMethods=" + defaultMethods)), element("arg", attribute("value", "-Dretrolambda.quiet=" + quiet)), element("arg", attribute("value", "-Dretrolambda.inputDir=" + getInputDir().getAbsolutePath())), element("arg", attribute("value", "-Dretrolambda.outputDir=" + getOutputDir().getAbsolutePath())), element("arg", attribute("value", "-Dretrolambda.classpathFile=" + classpathFile)), element("arg", attribute("value", "-javaagent:" + retrolambdaJar)), element("arg", attribute("value", "-jar")), element("arg", attribute("value", retrolambdaJar))))), executionEnvironment(project, session, pluginManager)); } finally { if (!classpathFile.delete()) { getLog().warn("Unable to delete " + classpathFile); } } } private void retrieveRetrolambdaJar(String version) throws MojoExecutionException { // TODO: use Maven's built-in artifact resolving, so that we can refer to retrolambda.jar in the local repository without copying it executeMojo( plugin(groupId("org.apache.maven.plugins"), artifactId("maven-dependency-plugin"), version("2.8")), goal("copy"), configuration(element("artifactItems", element("artifactItem", element(name("groupId"), "net.orfjackal.retrolambda"), element(name("artifactId"), "retrolambda"), element(name("version"), version), element(name("overWrite"), "true"), element(name("outputDirectory"), getRetrolambdaJarDir()), element(name("destFileName"), getRetrolambdaJarName())))), executionEnvironment(project, session, pluginManager)); } String getJavaCommand() { String javaCommand = getJavaCommand(new File(System.getProperty("java.home"))); Toolchain tc = toolchainManager.getToolchainFromBuildContext("jdk", session); if (tc != null) { getLog().info("Toolchain in retrolambda-maven-plugin: " + tc); javaCommand = tc.findTool("java"); } if (java8home != null) { if (tc != null) { getLog().warn("Toolchains are ignored, 'java8home' parameter is set to " + java8home); } javaCommand = getJavaCommand(java8home); } return javaCommand; } private static String getJavaCommand(File javaHome) { return new File(javaHome, "bin/java").getPath(); } private String getClasspath() { try { return Joiner.on(File.pathSeparator).join(getClasspathElements()); } catch (DependencyResolutionRequiredException e) { throw new RuntimeException(e); } } private File getClasspathFile() { try { String classpath = Joiner.on("\n").join(getClasspathElements()); File file = File.createTempFile("retrolambda", "classpath"); file.deleteOnExit(); Files.write(classpath, file, Charsets.UTF_8); return file; } catch (DependencyResolutionRequiredException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } private String getRetrolambdaJarPath() { return getRetrolambdaJarDir() + "/" + getRetrolambdaJarName(); } private String getRetrolambdaJarDir() { return project.getBuild().getDirectory() + "/retrolambda"; } private String getRetrolambdaJarName() { return "retrolambda.jar"; } private static String getRetrolambdaVersion() throws MojoExecutionException { try { InputStream is = ProcessClassesMojo.class.getResourceAsStream( "/META-INF/maven/net.orfjackal.retrolambda/retrolambda-maven-plugin/pom.properties"); try { Properties p = new Properties(); p.load(is); return p.getProperty("version"); } finally { is.close(); } } catch (IOException e) { throw new MojoExecutionException("Failed to detect the Retrolambda version", e); } } }